'''
Created on 2011-07-13
@author: conan
'''
import threading
import time
from SP import SP, Schedule
from Task import Task, Selection, Allocation, Process, Push, Pop, Dispatch
from utils import Stat, Timer, saveObj



class SF(threading.Thread):
    '''
    Class for Schedule-flow.
    @ivar tasks: a schedule-flow contains a set of tasks
    @type tasks: C{list} of L{Task}
    @ivar sp: the schedule problem instance associated with the schedule-flow
    @type sp: L{SP}
    @ivar stat: statistics of runtime performance
    @type stat: L{Stat}
    @ivar lastRun: last running task
    @ivar active: current running task
    '''
    def __init__(self, problem=None):
        threading.Thread.__init__(self)
        self.lastRun = None
        self.active = None
        self.tasks = {}
        self.sp = problem or SP()
  
        #Controls
        self.debug = False
        self._pause = False
        self._log = None
        self.stat = Stat()
        self.timer = Timer() 
                
      
    def connect(self, t1, t2, expr='lambda:True'):
        '''
        Connects two tasks (t1, t2) in a schedule-flow.
        @param t1: the name of the first task
        @param t2: the name of the second task
        @param expr: the precondition for t2. expr will be re-evaluated each time t1 is completed and 
        only if expr returns True, t2 can be executed.
        @type expr: C{str} of the expression
        '''  
        assert isinstance(t1, str) and isinstance(t2, str), 'given tasks names must be strings.'
        assert t1 in self.tasks and t2 in self.tasks, '%s or %s does not exist' % (t1, t2)
        assert isinstance(expr, str) and expr.startswith('lambda'), 'the argument "expr" must be a string of lambda expression'
        
        #expr is evaluated with some global arguments including tasks names, sp ...
        arguments = dict(self.tasks)
        arguments.update(sp=self.sp)
        precondition = eval(expr, arguments)
        if expr != 'lambda:True':
            self.tasks[t1].succs.insert(0, (self.tasks[t2], precondition))
        else:
            self.tasks[t1].succs.append((self.tasks[t2], precondition))
            
        
    def addTask(self, t):
        '''
        Add a task in the schedule-flow
        '''
        assert t.name not in self.tasks, 'task name already exists'
        assert isinstance(t, Task), '%s is not a task' % t
        t.sf = self
        t.sp = self.sp
        if not self.tasks:
            self.active = t
        self.tasks[t.name] = t
    
    
    def executeScript(self, script):
        '''
        Execute a script with given arguments "sp" and "sf". 
        "sf" and "sp" are predefined variables that represent the schedule-flow
        and its scheduling problem respectively.
        '''
        try:
            sf = self #@UnusedVariable
            sp = self.sp #@UnusedVariable
            exec script 
        except Exception as e:
            print 'Error executing the script "%s"\n%s' % (script, e)
    
    
    def run(self): 
        '''Used to execute the schedule-flow'''
        assert self.sp, 'Problem is not formulated'  
        self.sp.finalize()    
        assert self.active, 'First task unspecified' 
        self.timer.start() 
        while self.active:
            if not self._pause:
                #Timer
                middle = self.active.run()
                self.lastRun = self.active
                self.active = middle
            else:
                #Pause
                self.timer.pause()
                time.sleep(4)
            
        print 'Termination'
        print 'Best schedule found with:', self.sp.best
        print 'The schedule is saved to a binary file "%s"' % (self.sp.directory + 'best.sch')
        self.timer.pause()

    
    def togglePause(self, value=None):
        '''
        Pause or resume the execution of a schedule-flow
        '''
        if value is None:
            self._pause = not self._pause
        else:
            self._pause = value
        if self._pause: print 'You have paused the execution'
        else: print 'Execution is resumed'
        
    def log(self, line):
        '''
        Write something down to the log file
        '''
        if self._log is None:
            self._log = open(self.sp.directory + 'log.txt', 'w')
            self._log.write('Exapanded,cost,time\n')
        else:
            self._log.write(line) 
    def __repr__(self):
        return 'A schedule flow with %s tasks: %s' % (len(self.tasks), self.tasks.keys())


def jobshop(): 
    '''Test on the jobshop case'''
    sp = SP()
    sf = SF()
    sf.sp = sp
#    sf.debug = True
    
    sp.directory = '../Case/jobshop10by10/'
    sp.jobSrc = 'jobs.csv'
    sp.machineSrc = '10'
    sp.heuristic = lambda sch:50 * (100 - sch.numScheduled) 

    #Initialization
    sf.addTask(Selection(name='select'))
    sf.addTask(Allocation(name='allocate'))
    sf.addTask(Process('process'))
    sf.addTask(Push(name='push'))
    sf.addTask(Pop('pop'))
   
    #Configuration
    sf.tasks['select'].pd = Schedule.isEnabled
    sf.tasks['select'].jobs = sp.J
        
    #Binding
    #remember that the order matters 
    sf.connect('select', 'allocate')

    sf.connect('select', 'process', 'lambda: not select.output')
    sf.connect('allocate', 'push')
    sf.connect('process', 'push')
    sf.connect('push', 'pop')
    sf.connect('pop', 'select', 'lambda: pop.numExec < 30000')


    sf.run()
    print sf.stat
    print sf.timer.showAll()




def spt():
    '''Test on the jobshop case'''
    sp = SP()
    sf = SF()
    sf.sp = sp
    
    sp.directory = '../Case/jobshop10by10/'
    sp.jobSrc = 'jobs.csv'
    sp.machineSrc = '10'
#    sp.heuristic = lambda sch:50 * (100 - sch.numScheduled) 

    SPT = Dispatch("SPT")
    sf.addTask(SPT)
    SPT.pd = lambda sch, job: sch.isEnabled(job)

  
    SPT.rule = Dispatch.SPT
    sf.connect("SPT", "SPT", "lambda:not sp.current.isGoal()")
    sf.run()
    
    print sf.stat
    print sf.timer.showAll()
    print sp.best.events()


if __name__ == '__main__':
    jobshop()
#    spt()
#    tsp2()
